﻿using System;
using System.Collections.Generic;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Xnaml {

	[System.Windows.Markup.ContentProperty( "XAMLHost" )]
	public partial class Game : Canvas, IDisposable {
		#region Fields

		private static List<Game> games = new List<Game>( );
		private GameElement gamehost = null;
		private List<IUpdateable> updatables = new List<IUpdateable>( );
		private List<IDrawable> drawables = new List<IDrawable>( );
		private readonly TimeSpan maxelapsed = TimeSpan.FromMilliseconds( 500 );
		private TimeSpan targetelapsedtime = TimeSpan.FromSeconds( 1.0 / 60.0 );
		private TimeSpan inactiveSleepTime = TimeSpan.FromMilliseconds( 20 );
		private GameServiceContainer gameservices = new GameServiceContainer();
		private DispatcherTimer ticker = null;
		private DispatcherTimer renderer = null;
		private ContentManager content = null;
		private GameTime gameTime = new GameTime();
		private GameClock clock = null;
		private TimeSpan elapsedreal = TimeSpan.Zero;
		private TimeSpan lastframeelapsedgame = TimeSpan.Zero;
		private TimeSpan targetelapsedgame = TimeSpan.Zero;
		private TimeSpan totaltimegame = TimeSpan.Zero;
		private TimeSpan accumulatedelapsedgame = TimeSpan.Zero;
		private IGraphicsDeviceManager graphicsdevicemanager = null;
		private IGraphicsDeviceService graphicsdeviceservice = null;
		private LaunchParameters launcherparameters = new LaunchParameters();
		private GameResizeMode resizeclippingmode = GameResizeMode.Clip;
		private bool currentlyrunning = false;
		private bool fixedtimestep = true;
		private bool mousevisible = false;
		private bool premierupdate = true;
		private bool immediateexit = false;
		private bool activated = false;
		private bool framelag = false;
		private bool suppressdraw = false;
		private bool ticking = false, rendering = false;
		
		#endregion Fields

		#region Events

		public event EventHandler<BroadcastEventArgs> Broadcast;
		public event EventHandler<EventArgs> Activated;
		public event EventHandler<EventArgs> Deactivated;
		public event EventHandler<EventArgs> Disposed;
		public event EventHandler<EventArgs> Exiting;
		public event EventHandler<EventArgs> DeviceCreated;
		public event EventHandler<EventArgs> DeviceReset;
		public event EventHandler<EventArgs> DeviceResetting;
		public event EventHandler<EventArgs> DeviceDisposing;
		public event EventHandler Resized;
		public new event System.Windows.Input.MouseButtonEventHandler MouseDown;
		public new event System.Windows.Input.MouseButtonEventHandler MouseUp;
		public new event System.Windows.Input.MouseEventHandler MouseEnter;
		public new event System.Windows.Input.MouseEventHandler MouseLeave;
		public new event System.Windows.Input.MouseWheelEventHandler MouseWheel;
		public new event System.Windows.Input.MouseEventHandler MouseMove;
		public new event System.Windows.Input.MouseButtonEventHandler MouseLeftButtonDown;
		public new event System.Windows.Input.MouseButtonEventHandler MouseRightButtonDown;
		public new event System.Windows.Input.MouseButtonEventHandler MouseLeftButtonUp;
		public new event System.Windows.Input.MouseButtonEventHandler MouseRightButtonUp;
		public new event System.Windows.Input.MouseButtonEventHandler PreviewMouseDown;
		public new event System.Windows.Input.MouseButtonEventHandler PreviewMouseUp;
		public new event System.Windows.Input.MouseWheelEventHandler PreviewMouseWheel;
		public new event System.Windows.Input.MouseEventHandler PreviewMouseMove;
		public new event System.Windows.Input.MouseButtonEventHandler PreviewMouseLeftButtonDown;
		public new event System.Windows.Input.MouseButtonEventHandler PreviewMouseRightButtonDown;
		public new event System.Windows.Input.MouseButtonEventHandler PreviewMouseLeftButtonUp;
		public new event System.Windows.Input.MouseButtonEventHandler PreviewMouseRightButtonUp;
		public new event KeyboardEventHandler KeyDown;
		public new event KeyboardEventHandler KeyUp;
		public new event KeyboardFocusChangedEventHandler GotKeyboardFocus;
		public new event KeyboardFocusChangedEventHandler LostKeyboardFocus;
		public new event KeyboardEventHandler PreviewKeyDown;
		public new event KeyboardEventHandler PreviewKeyUp;
		public new event KeyboardFocusChangedEventHandler PreviewGotKeyboardFocus;
		public new event KeyboardFocusChangedEventHandler PreviewLostKeyboardFocus;

		protected virtual void OnActivated ( object sender, EventArgs e ) {
			if ( !IsRunning )
				Run();
			if ( Activated != null ) {
				Activated( this, e );
			}
			if ( IsRunning ) {
				ticker.Start();
			}
		}

		protected virtual bool ShowMissingRequirementsMessage ( Exception exception ) {
			if ( MessageBox.Show( "Missing Requirement:\n" + exception.Message, "Missing XNA Requirement" ) != MessageBoxResult.None ) {
				return true;
			}
			throw exception;
		}

		protected virtual void OnDeactivated ( object sender, EventArgs e ) {
			if ( Deactivated != null ) {
				Deactivated( this, e );
			}
			ticker.Stop();
		}

		protected virtual void OnExiting ( object sender, EventArgs e ) {
			UnhookDeviceEvents();
			if ( Exiting != null ) {
				Exiting( null, e );
			}
		}

		protected virtual void OnBroadcast ( object sender, BroadcastEventArgs e ) {
			if ( Broadcast != null ) {
				Broadcast( this, e );
			}
		}

		protected virtual void OnDeviceCreated ( object sender, EventArgs e ) {
			if ( DeviceCreated != null ) {
				DeviceCreated( sender, e );
			}
		}

		protected virtual void OnDeviceReset ( object sender, EventArgs e ) {
			if ( DeviceReset != null ) {
				DeviceReset( sender, e );
			}
		}

		protected virtual void OnDeviceResetting ( object sender, EventArgs e ) {
			if ( DeviceResetting != null ) {
				DeviceResetting( sender, e );
			}
		}

		protected virtual void OnDeviceDisposing ( object sender, EventArgs e ) {
			if ( DeviceDisposing != null ) {
				DeviceDisposing( sender, e );
			}
		}

		protected virtual void OnDisposed ( object sender, EventArgs e ) {
			if ( Disposed != null ) {
				Disposed( null, e );
			}
		}

		internal virtual void iOnResized ( EventArgs e ) {
			if ( Resized != null )
				Resized( this, e );
		}

		internal virtual void iOnMouseDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( MouseDown != null )
				MouseDown( this, e );
		}

		internal virtual void iOnMouseUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( MouseUp != null )
				MouseUp( this, e );
		}

		internal virtual void iOnMouseEnter ( System.Windows.Input.MouseEventArgs e ) {
			if ( MouseEnter != null )
				MouseEnter( this, e );
		}

		internal virtual void iOnMouseLeave ( System.Windows.Input.MouseEventArgs e ) {
			if ( MouseLeave != null )
				MouseLeave( this, e );
		}

		internal virtual void iOnMouseWheel ( System.Windows.Input.MouseWheelEventArgs e ) {
			if ( MouseWheel != null )
				MouseWheel( this, e );
		}

		internal virtual void iOnMouseMove ( System.Windows.Input.MouseEventArgs e ) {
			if ( MouseMove != null )
				MouseMove( this, e );
		}

		internal virtual void iOnMouseLeftButtonDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( MouseLeftButtonDown != null )
				MouseLeftButtonDown( this, e );
		}

		internal virtual void iOnMouseRightButtonDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( MouseRightButtonDown != null )
				MouseRightButtonDown( this, e );
		}

		internal virtual void iOnMouseLeftButtonUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( MouseLeftButtonUp != null )
				MouseLeftButtonUp( this, e );
		}

		internal virtual void iOnMouseRightButtonUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( MouseRightButtonUp != null )
				MouseRightButtonUp( this, e );
		}

		internal virtual void iOnPreviewMouseDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( PreviewMouseDown != null )
				PreviewMouseDown( this, e );
		}

		internal virtual void iOnPreviewMouseUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( PreviewMouseUp != null )
				PreviewMouseUp( this, e );
		}

		internal virtual void iOnPreviewMouseWheel ( System.Windows.Input.MouseWheelEventArgs e ) {
			if ( PreviewMouseWheel != null )
				PreviewMouseWheel( this, e );
		}

		internal virtual void iOnPreviewMouseMove ( System.Windows.Input.MouseEventArgs e ) {
			if ( PreviewMouseMove != null )
				PreviewMouseMove( this, e );
		}

		internal virtual void iOnPreviewMouseLeftButtonDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( PreviewMouseLeftButtonDown != null )
				PreviewMouseLeftButtonDown( this, e );
		}

		internal virtual void iOnPreviewMouseRightButtonDown ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( PreviewMouseRightButtonDown != null )
				PreviewMouseRightButtonDown( this, e );
		}

		internal virtual void iOnPreviewMouseLeftButtonUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( PreviewMouseLeftButtonUp != null )
				PreviewMouseLeftButtonUp( this, e );
		}

		internal virtual void iOnPreviewMouseRightButtonUp ( System.Windows.Input.MouseButtonEventArgs e ) {
			if ( PreviewMouseRightButtonUp != null )
				PreviewMouseRightButtonUp( this, e );
		}

		internal virtual void iOnKeyDown ( KeyEventArgs e ) {
			if ( KeyDown != null )
				KeyDown( this, e );
		}

		internal virtual void iOnKeyUp ( KeyEventArgs e ) {
			if ( KeyUp != null )
				KeyUp( this, e );
		}

		internal virtual void iOnGotKeyboardFocus ( KeyboardFocusChangedEventArgs e ) {
			if ( GotKeyboardFocus != null )
				GotKeyboardFocus( this, e );
		}

		internal virtual void iOnLostKeyboardFocus ( KeyboardFocusChangedEventArgs e ) {
			if ( LostKeyboardFocus != null )
				LostKeyboardFocus( this, e );
		}

		internal virtual void iOnPreviewKeyDown ( KeyEventArgs e ) {
			if ( PreviewKeyDown != null )
				PreviewKeyDown( this, e );
		}

		internal virtual void iOnPreviewKeyUp ( KeyEventArgs e ) {
			if ( PreviewKeyUp != null )
				PreviewKeyUp( this, e );
		}

		internal virtual void iOnPreviewGotKeyboardFocus ( KeyboardFocusChangedEventArgs e ) {
			if ( PreviewGotKeyboardFocus != null )
				PreviewGotKeyboardFocus( this, e );
		}

		internal virtual void iOnPreviewLostKeyboardFocus ( KeyboardFocusChangedEventArgs e ) {
			if ( PreviewLostKeyboardFocus != null )
				PreviewLostKeyboardFocus( this, e );
		}

		#endregion Events

		#region Properties

		public static List<Game> Games {
			get {
				return games;
			}
		}

		private new UIElementCollection Children {
			get;
			set;
		}

		public object XAMLHost {
			get {
				if ( !DesignTime )
					return this.Window.XAMLHost;
				else
					return ( base.Children[ 0 ] as ContentControl ).Content;
			}
			set {
				if ( !DesignTime )
					this.Window.XAMLHost = value;
				else
					( base.Children[ 0 ] as ContentControl ).Content = value;
			}
		}

		public GraphicsDevice GraphicsDevice {
			get {
				graphicsdeviceservice = (IGraphicsDeviceService)Services.GetService( typeof( IGraphicsDeviceService ) );
				if ( graphicsdeviceservice == null ) {
					throw new InvalidOperationException( Properties.Resources.NoGraphicsDeviceService );
				}
				return graphicsdeviceservice.GraphicsDevice;
			}
		}

		public bool IsRunning {
			get;
			private set;
		}

		public new bool IsKeyboardFocused {
			get {
				return gamehost.IsKeyboardFocused;
			}
		}

		public new bool IsFocused {
			get {
				return gamehost.IsFocused;
			}
		}

		public bool KeyboardFocused {
			get {
				return gamehost.IsKeyboardFocused;
			}
		}

		public bool MouseFocused {
			get {
				return gamehost.IsMouseFocused;
			}
		}

		public GraphicsProfile GraphicsProfile {
			get {
				if (DesignTime)
					return Xnaml.GraphicsDeviceManager.DefaultGraphicsProfile;
				return GraphicsDevice.GraphicsProfile;
			}
			set {
				Xnaml.GraphicsDeviceManager.DefaultGraphicsProfile = value;
			}
		}

		public ContentManager Content {
			get {
				return content;
			}
			set {
				if ( value == null ) {
					throw new ArgumentNullException();
				}
				content = value;
			}
		}

		public GameComponentCollection Components {
			get;
			private set;
		}

		public TimeSpan InactiveSleepTime {
			get {
				return inactiveSleepTime;
			}
			set {
				if ( value <= TimeSpan.Zero ) {
					throw new ArgumentOutOfRangeException( "Resources.InactiveSleepTimeCannotBeZero", "value" );
				}
				inactiveSleepTime = value;
			}
		}

		public bool IsFixedTimeStep {
			get {
				return fixedtimestep;
			}
			set {
				if ( fixedtimestep != value ) {
					fixedtimestep = value;
					RecomputeStepSpan();
				}
			}
		}

		public TimeSpan TargetElapsedTime {
			get {
				return targetelapsedtime;
			}
			set {
				targetelapsedtime = value;
				RecomputeStepSpan();
			}
		}

		public GameServiceContainer Services {
			get;
			private set;
		}

		public bool IsActive {
			get {
				return activated;
			}
			internal set {
				activated = value;
				if ( value )
					OnActivated( this, EventArgs.Empty );
				else
					OnDeactivated( this, EventArgs.Empty );
			}
		}

		public GameElement Window {
			get {
				return gamehost;
			}
		}

		public GameResizeMode ResizeClippingMode {
			get {
				return resizeclippingmode;
			}
			set {
				resizeclippingmode = value;
			}
		}

		public bool ClipToWindow {
			get {
				return ( resizeclippingmode & GameResizeMode.Clip ) == GameResizeMode.Clip;
			}
			set {
				if ( value )
					resizeclippingmode |= GameResizeMode.Clip;
				else
					resizeclippingmode &= ~GameResizeMode.Clip;
			}
		}

		public bool ShrinkToFit {
			get {
				return ( resizeclippingmode & GameResizeMode.Shrink ) == GameResizeMode.Shrink;
			}
			set {
				if ( value )
					resizeclippingmode |= GameResizeMode.Shrink;
				else
					resizeclippingmode &= ~GameResizeMode.Shrink;
			}
		}

		public bool DesignTime {
			get {
				return System.ComponentModel.DesignerProperties.GetIsInDesignMode( this );
			}
		}

		public LaunchParameters LaunchParameters {
			get {
				return launcherparameters;
			}
		}

		public bool IsMouseVisible {
			get {
				return mousevisible;
			}
			set {
				mousevisible = value;
				if ( value )
					Window.Cursor = System.Windows.Input.Cursors.None;
				else
					Window.Cursor = System.Windows.Input.Cursors.Arrow;
			}
		}

		#endregion Properties

		#region Constructors

		public Game ( ) {
			if ( !DesignTime ) {
				string[] launchargs = Environment.GetCommandLineArgs( );
				launcherparameters = new LaunchParameters( );
				if ( launchargs != null ) {
					for ( int f = 0; f < launchargs.Length; ++f ) {
						launcherparameters.Add( launchargs[ f ], "" );
					}
				}
				gamehost = new GameElement( this );
				gamehost.Closed += new EventHandler( WindowClosed );
				ticker = new DispatcherTimer();
				renderer = new DispatcherTimer( );
				renderer.Tick += new EventHandler( Render );
				ticker.Tick += new EventHandler( Tick );
				Background = new SolidColorBrush( System.Windows.Media.Color.FromArgb( 0, 255, 255, 255 ) );
				IsHitTestVisible = true;
				IsVisibleChanged += new DependencyPropertyChangedEventHandler( GameVisible );

				Components = new GameComponentCollection();
				Components.ComponentAdded += new EventHandler<GameComponentCollectionEventArgs>( GameComponentAdded );
				Components.ComponentRemoved += new EventHandler<GameComponentCollectionEventArgs>( GameComponentRemoved );

				Services = new GameServiceContainer();
				content = new ContentManager( Services );

				IsFixedTimeStep = true;

				clock = new GameClock();
				totaltimegame = TimeSpan.Zero;
				accumulatedelapsedgame = TimeSpan.Zero;
				lastframeelapsedgame = TimeSpan.Zero;

				targetelapsedgame = TimeSpan.FromTicks( 0x28b0aL );
				
				games.Add( this );
			}
			else
				base.Children.Add( new ContentControl() );
		}

		#endregion Constructors

		private void WindowClosed ( object sender, EventArgs e ) {
			OnExiting( this, EventArgs.Empty );
			OnDisposed( this, EventArgs.Empty );
		}

		private void GameVisible ( object sender, DependencyPropertyChangedEventArgs e ) {
			IsActive = (bool)e.NewValue;
		}

		/// <summary>
		/// A public function for sending a message to a game class.
		/// </summary>
		/// <typeparam name="T">The type of data to send.</typeparam>
		/// <param name="sender">The sender of the data.</param>
		/// <param name="data">The data itself.</param>
		public virtual void SendMessage<T> ( object sender, T data ) {
			MessageReceived( sender, ref data );
		}

		/// <summary>
		/// A public function for sending a message to a game class.
		/// </summary>
		/// <typeparam name="T">The type of data to send.</typeparam>
		/// <param name="sender">The sender of the data.</param>
		/// <param name="ref data">The data itself.</param>
		public virtual void SendMessage<T> ( object sender, ref T data ) {
			MessageReceived( sender, ref data );
		}
		
		/// <summary>
		/// An aribtrary function for getting data from a SendMessage() call.
		/// This is useful only when the Game is in a separate asembly/library
		/// and cannot communicate with the WPF Application directly.
		/// </summary>
		/// <typeparam name="T">The type of data to the game will recieve when SendMessage() is called on Game.</typeparam>
		/// <param name="sender">The sender of the data.</param>
		/// <param name="ref data">The data retrieved from SendMessage().</param>
		protected virtual void MessageReceived<T> ( object sender, ref T data ) {

		}

		/// <summary>
		/// Sends out data using a Broadcast event. Useful when a WPF Application needs
		/// information from a game class but it cannot get that information directly.
		/// </summary>
		/// <typeparam name="T">The type of data to be broadcast on the Broadcast Event.</typeparam>
		/// <param name="sender">The sender of the data.</param>
		/// <param name="data">The data itself.</param>
		protected virtual void BroadcastMessage<T> ( object sender, T data ) {
			OnBroadcast( sender, new BroadcastEventArgs( data ) );
		}

		public void Run ( ) {
			graphicsdevicemanager = (IGraphicsDeviceManager)Services.GetService( typeof( IGraphicsDeviceManager ) );
			if ( graphicsdevicemanager != null ) {
				graphicsdevicemanager.CreateDevice();
			}
			else {
				throw new Exception( "No Graphics Device Manager is present." );
			}
			graphicsdeviceservice = (IGraphicsDeviceService)Services.GetService( typeof( IGraphicsDeviceService ) );
			if ( graphicsdeviceservice == null ) {
				throw new Exception( "There is no Graphics Device Service registered for this game." );
			}
			Initialize();
			currentlyrunning = true;
			try {
				BeginRun();
				gameTime = new GameTime(
					totaltimegame, clock.CurrentTime,
					false
				);
				if ( !premierupdate ) {
					Update( gameTime );
					premierupdate = true;
				}
				if ( Window != null ) {
					Window.Owner = Application.Current.MainWindow;
					Window.PreRun();
				}
				IsRunning = true;
			}
			catch {
				throw new Exception( "Game Run Failure: Could not initialize Update and Window." );
			}
			finally {
				currentlyrunning = false;
			}
		}

		public void SuppressDraw ( ) {
			suppressdraw = true;
		}

		public void RunOneFrame ( ) {
			Tick( );
		}

		internal void RecomputeStepSpan ( ) {
			ticker.Interval = fixedtimestep ? targetelapsedtime : new TimeSpan( 0, 0, 0, 0, 1 );
		}

		internal void RenderToRetrace ( ) {
			if ( graphicsdeviceservice.GraphicsDevice.PresentationParameters.PresentationInterval != PresentInterval.Immediate ) {
				renderer.Stop( );
				CompositionTarget.Rendering += new EventHandler( Render );
			}
			else {
				renderer.Interval = new TimeSpan( 0, 0, 0, 0, 1 );
				renderer.Start( );
				CompositionTarget.Rendering -= new EventHandler( Render );
			}
		}
		
		protected virtual bool BeginDraw ( ) {
			return !suppressdraw && graphicsdevicemanager != null &&
				graphicsdevicemanager.BeginDraw();
		}

		protected virtual void BeginRun ( ) {

		}

		private void Render ( object sender, EventArgs e ) {
			Render( );
		}

		private void Render ( ) {
			if ( rendering )
				return;
			
			rendering = true;
			try {
				if ( !immediateexit && premierupdate && BeginDraw( ) ) {
					gameTime = new GameTime( clock.CurrentTime, clock.ElapsedTime, gameTime.IsRunningSlowly );
					Draw( gameTime );
					EndDraw( );
					rendering = false;
				}
			}
			catch ( Exception e ) {
				throw new GameDrawException( "There was an error during the game's draw loop:\n" + e.Message, e );
			}
			finally {
				rendering = false;
			}
		}

		protected virtual void EndDraw ( ) {
			if ( graphicsdeviceservice != null && graphicsdeviceservice.GraphicsDevice != null ) {
				Microsoft.Xna.Framework.Rectangle rect =
					new Microsoft.Xna.Framework.Rectangle( 0, 0, (int)Math.Ceiling( DesiredSize.Width ), (int)Math.Ceiling( DesiredSize.Height ) );
				int topmostright = (int)( Window.TopLevelWindow.Left + Window.TopLevelWindow.Width - 8 );
				int topmostbottom = (int)( Window.TopLevelWindow.Top + Window.TopLevelWindow.Height - 8 );
				if ( Window.Left + rect.Width > topmostright ) {
					rect.Width -= ( (int)( Window.Left + rect.Width ) - topmostright );
					if ( rect.Width < 1 ) {
						return;
					}
				}
				if ( Window.Top + rect.Height > topmostbottom ) {
					rect.Height -= ( (int)( Window.Top + rect.Height ) - topmostbottom );
					if ( rect.Height < 1 ) {
						return;
					}
				}
				if ( ShrinkToFit ) {
					graphicsdeviceservice.GraphicsDevice.Present( null, rect, Window.Handle );
				}
				else {
					graphicsdeviceservice.GraphicsDevice.Present( rect, rect, Window.Handle );
				}
			}
		}

		protected virtual void EndRun ( ) {

		}

		private void Tick ( object sender, EventArgs e ) {
			Tick( );
		}

		public void Tick ( ) {
			if ( ticking )
				return;

			ticking = true;
			if ( !immediateexit ) {
				if ( !IsActive ) {
					Thread.Sleep( (int)inactiveSleepTime.TotalMilliseconds );
				}
				else
					clock.Step();
				elapsedreal = clock.ElapsedTime;
				if ( elapsedreal < TimeSpan.Zero ) {
					elapsedreal = TimeSpan.Zero;
				}
				if ( elapsedreal > maxelapsed ) {
					elapsedreal = maxelapsed;
				}

				gameTime = new GameTime( clock.CurrentTime, clock.ElapsedTime, gameTime.IsRunningSlowly );
				framelag = false;

				if ( fixedtimestep ) {
					accumulatedelapsedgame += elapsedreal;
					long tickweight = accumulatedelapsedgame.Ticks / targetelapsedgame.Ticks;
					accumulatedelapsedgame = TimeSpan.FromTicks( accumulatedelapsedgame.Ticks % targetelapsedgame.Ticks );
					lastframeelapsedgame = TimeSpan.Zero;
					TimeSpan targetElapsedTime = targetelapsedgame;
					if ( tickweight > 0 ) {
						while ( tickweight > 1 ) {
							framelag = true;
							gameTime = new GameTime( gameTime.TotalGameTime, gameTime.ElapsedGameTime, framelag );
							tickweight--;
							try {
								gameTime = new GameTime( totaltimegame, targetElapsedTime, gameTime.IsRunningSlowly );
								if ( suppressdraw ) {
									suppressdraw = false;
								}
								Update( gameTime );
								continue;
							}
							catch ( Exception e ) {
								throw new GameUpdateException( "There was an error during the game's update loop:\n" + e.Message, e );
							}
							finally {
								lastframeelapsedgame += targetElapsedTime;
								totaltimegame += targetElapsedTime;
							}
						}
						gameTime = new GameTime(
							gameTime.TotalGameTime, gameTime.ElapsedGameTime,
							false
						);
						try {
							gameTime = new GameTime( totaltimegame, targetElapsedTime, gameTime.IsRunningSlowly );
							if ( suppressdraw ) {
								suppressdraw = false;
							}
							Update( gameTime );
						}
						catch ( Exception e ) {
							throw new GameUpdateException( "There was an error during the game's update loop:\n" + e.Message, e );
						}
						finally {
							lastframeelapsedgame += targetElapsedTime;
							totaltimegame += targetElapsedTime;
						}
					}
				}
				else {
					TimeSpan elapsedRealTime = elapsedreal;
					try {
						gameTime = new GameTime(
							totaltimegame, lastframeelapsedgame = elapsedRealTime,
							gameTime.IsRunningSlowly
						);
						Update( gameTime );
					}
					catch ( Exception e ) {
						throw new GameUpdateException( "There was an error during the game's update loop:\n" + e.Message, e );
					}
					finally {
						totaltimegame += elapsedRealTime;
					}
				}
			}
			ticking = false;
		}

		public void ResetElapsedTime ( ) {
			clock.ResetElapsed();
		}

		public void Exit ( ) {
			OnExiting( this, EventArgs.Empty );
			immediateexit = true;
			IsRunning = false;
			ticker.Stop( );
			renderer.Stop( );
			Window.Exit( );
			Dispose( );
		}

		#region Device life

		private void HookDeviceEvents ( ) {
			graphicsdeviceservice = (IGraphicsDeviceService)Services.GetService( typeof( IGraphicsDeviceService ) );
			if ( graphicsdeviceservice != null ) {
				graphicsdeviceservice.DeviceCreated += OnDeviceCreated;
				graphicsdeviceservice.DeviceResetting += OnDeviceResetting;
				graphicsdeviceservice.DeviceReset += OnDeviceReset;
				graphicsdeviceservice.DeviceDisposing += OnDeviceDisposing;
				RenderToRetrace( );
			}
		}

		private void UnhookDeviceEvents ( ) {
			graphicsdeviceservice = (IGraphicsDeviceService)Services.GetService( typeof( IGraphicsDeviceService ) );
			if ( graphicsdeviceservice != null ) {
				graphicsdeviceservice.DeviceCreated -= OnDeviceCreated;
				graphicsdeviceservice.DeviceResetting -= OnDeviceResetting;
				graphicsdeviceservice.DeviceReset -= OnDeviceReset;
				graphicsdeviceservice.DeviceDisposing -= OnDeviceDisposing;
				CompositionTarget.Rendering -= new EventHandler( Render );
				renderer.Stop( );
			}
		}

		#endregion Device life

		#region Components life

		private void GameComponentAdded ( object sender, GameComponentCollectionEventArgs e ) {
			if ( currentlyrunning ) {
				e.GameComponent.Initialize();
			}
			IUpdateable item = e.GameComponent as IUpdateable;
			if ( item != null ) {
				int num = updatables.BinarySearch( item, UpdateOrderComparer.Default );
				if ( num < 0 ) {
					updatables.Insert( ~num, item );
					item.UpdateOrderChanged += new EventHandler<EventArgs>( UpdateableUpdateOrderChanged );
				}
			}
			IDrawable drawable = e.GameComponent as IDrawable;
			if ( drawable != null ) {
				int num2 = drawables.BinarySearch( drawable, DrawOrderComparer.Default );
				if ( num2 < 0 ) {
					drawables.Insert( ~num2, drawable );
					drawable.DrawOrderChanged += new EventHandler<EventArgs>( DrawableDrawOrderChanged );
				}
			}
		}

		private void GameComponentRemoved ( object sender, GameComponentCollectionEventArgs e ) {
			IUpdateable item = e.GameComponent as IUpdateable;
			if ( item != null ) {
				updatables.Remove( item );
				item.UpdateOrderChanged -= new EventHandler<EventArgs>( UpdateableUpdateOrderChanged );
			}
			IDrawable drawable = e.GameComponent as IDrawable;
			if ( drawable != null ) {
				drawables.Remove( drawable );
				drawable.DrawOrderChanged -= new EventHandler<EventArgs>( DrawableDrawOrderChanged );
			}
		}

		private void UpdateableUpdateOrderChanged ( object sender, EventArgs e ) {
			IUpdateable item = sender as IUpdateable;
			updatables.Remove( item );
			int num = updatables.BinarySearch( item, UpdateOrderComparer.Default );
			if ( num < 0 ) {
				updatables.Insert( ~num, item );
			}
		}

		private void DrawableDrawOrderChanged ( object sender, EventArgs e ) {
			IDrawable item = sender as IDrawable;
			drawables.Remove( item );
			int num = drawables.BinarySearch( item, DrawOrderComparer.Default );
			if ( num < 0 ) {
				drawables.Insert( ~num, item );
			}
		}

		#endregion Components life

		#region Game Specific Methods

		/// <summary>
		/// <para>Called when graphics resources need to be loaded.	Override this method to load any game-specific graphics resources.</para>
		/// </summary>
		protected virtual void LoadContent ( ) {
		}

		protected virtual void UnloadContent ( ) {
		}

		protected virtual void Initialize ( ) {
			for ( int i = 0; i < Components.Count; i++ ) {
				Components[i].Initialize();
			}
			HookDeviceEvents();
			LoadContent();
		}

		protected virtual void Update ( GameTime gameTime ) {
			for ( int i = 0; i < updatables.Count; i++ ) {
				IUpdateable updateable = updatables[i];
				if ( updateable.Enabled ) {
					updateable.Update( gameTime );
				}
			}
		}

		protected virtual void Draw ( GameTime gameTime ) {
			for ( int i = 0; i < drawables.Count; i++ ) {
				IDrawable drawable = drawables[i];
				if ( drawable.Visible ) {
					drawable.Draw( gameTime );
				}
			}
		}

		#endregion Game Specific Methods

		#region IDisposable Members

		public void Dispose ( ) {
			lock ( this ) {
				IGameComponent[] array = new IGameComponent[Components.Count];
				Components.CopyTo( array, 0 );
				for ( int i = 0; i < array.Length; i++ ) {
					IDisposable disposablecomponent = array[i] as IDisposable;
					if ( disposablecomponent != null ) {
						disposablecomponent.Dispose();
					}
				}
				IDisposable graphicsdevicemanagerdisposable = graphicsdevicemanager as IDisposable;
				if ( graphicsdevicemanagerdisposable != null ) {
					graphicsdevicemanagerdisposable.Dispose();
				}
				UnhookDeviceEvents();

				if (!games.Remove(this)) {
					// Was never in the game list in the first place
				}

				if ( Disposed != null ) {
					Disposed( this, EventArgs.Empty );
				}
			}
		}

		#endregion IDisposable Members
	}
}